<h1 id="-">第十二章</h1>
<hr>
<h2 id="-modules-mixins-">模块（Modules）和混入（Mixins）</h2>
<p>在 Ruby 中，每个类只有一个直接的“父类”（parent），尽管每个父类可能有许多“子类”（children）。通过将类层次结构限制为单继承，Ruby 避免了那些允许多继承的编程语言（如 C++）中可能出现的一些问题。当类有很多父类和子类，而且它们的父类、子类还有其它父类和子类，你最终会面临一个难以理解的网络（或者“结”？），而不是你可能想要的整洁，有序的层次结构。</p>
<p>然而，有时候（多继承）对于与实现某些共享特征并不密切相关的类是有用的。例如，剑可能是一种武器（Weapon），但也会是一种珍宝（Treasure）；PC 可能是一种计算机（Computer），但也会是一种投资（Investment）等等。</p>
<p>但是，由于定义武器（Weapons）和珍宝（Treasures）或计算机（Computers）和投资（Investments）的类继承自不同的祖先类，因此它们的类层次结构使它们没有明显的方式来共享数据和方法。这个问题的 Ruby 解决方案由 Modules 提供。</p>
<h3 id="-">模块像一个类...</h3>
<p>模块（module）的定义看起来非常类似于类（class）。事实上，模块和类密切相关 -  <code>Module</code> 类是 <code>Class</code> 类的直接祖先。就像一个类一样，模块可以包含常量，方法和类。 这是一个简单的模块：</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">MyModule</span></span>
  GOODMOOD = <span class="hljs-string">"happy"</span>
  BADMOOD = <span class="hljs-string">"grumpy"</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">greet</span></span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"I'm <span class="hljs-subst">#{GOODMOOD}</span>. How are you?"</span>
  <span class="hljs-keyword">end</span>

<span class="hljs-keyword">end</span></code></pre><p>如你所见，它包含一个常量 <code>GOODMOOD</code> 和一个&#39;实例方法&#39;（instance method） <code>greet</code>。</p>
<h3 id="-">模块方法</h3>
<p>模块除了有实例方法（instance methods）之外，还可以具有模块方法（module methods）。就像类方法以类的名称为前缀一样，模块方法也以模块名称为前缀：</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">MyModule.greet</span></span>
  <span class="hljs-keyword">return</span> <span class="hljs-string">"I'm <span class="hljs-subst">#{BADMOOD}</span>. How are you?"</span>
<span class="hljs-keyword">end</span></code></pre><p>尽管它们有相似之处，但是有两个重要特征，类有而模块没有：<strong>实例（instances）</strong>和<strong>继承（inheritance）</strong>。类可以有实例（对象），超类（父类）和子类（子类）；模块没有这些。</p>
<div class="note">

<p>Module 类确实有一个超类 - 即 Object。 但是，你创建的任何命名模块都没有超类。有关模块（Modules）和类（Classes）之间关系的更详细说明，请参阅本章末尾的“<strong>深入探索</strong>”部分。</p>
</div>

<p>这为我们引出了下一个问题：如果你不能从模块创建一个对象，那么模块可以用来干什么？这可以用两个词来回答：<strong>命名空间（namespaces）</strong>和<strong>混入（mixins）</strong>。Ruby 的 &#39;mixins&#39; 机制提供了一种处理多重继承存在的问题的方法。我们很快就会遇到 mixins。首先，我们来看看命名空间（namespaces）。</p>
<h3 id="-">模块作为命名空间</h3>
<p>你可以将模块视为一种围绕一组方法，常量和类的命名“包装器”（wrapper）。模块内部的各种代码共享相同的“命名空间”（namespaces），因此它们彼此都可见，但对模块外部的代码不可见。</p>
<p>Ruby 类库定义了许多模块，如 Math 和 Kernel。Math 模块包含数学方法（例如 <code>sqrt</code> 以返回平方根）和常量（例如 <code>PI</code>）。Kernel 模块包含我们从一开始就使用的许多方法，例如 <code>print</code>，<code>puts</code> 和 <code>gets</code>。</p>
<p>假设我们前面看过的模块：</p>
<div class="code-file clearfix"><span>modules1.rb</span></div>

<pre><code><span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">MyModule</span></span>
  GOODMOOD = <span class="hljs-string">"happy"</span>
  BADMOOD = <span class="hljs-string">"grumpy"</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">greet</span></span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"I'm <span class="hljs-subst">#{GOODMOOD}</span>. How are you?"</span>
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">MyModule</span>.<span class="hljs-title">greet</span></span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"I'm <span class="hljs-subst">#{BADMOOD}</span>. How are you?"</span>
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span></code></pre><p>我们可以访问模块常量，就像我们使用 <code>::</code> 作用域解析运算符访问类常量一样，如下所示：</p>
<pre><code>puts(<span class="hljs-name">MyModule</span>:<span class="hljs-symbol">:GOODMOOD</span>)</code></pre><p>我们可以使用点表示法访问模块方法 - 即，指定模块名称后跟句点和方法名称。 以下会打印出来 &quot;I&#39;m grumpy. How are you?&quot;：</p>
<pre><code><span class="hljs-function"><span class="hljs-title">puts</span><span class="hljs-params">( MyModule.greet )</span></span></code></pre><h3 id="-">模块的“实例方法”</h3>
<p>但是如何访问实例方法，<code>greet</code>？ 由于模块定义了一个封闭的命名空间，模块外的任何代码都无法“看到” <code>greet</code> 方法，所以这不起作用：</p>
<pre><code><span class="hljs-function"><span class="hljs-title">puts</span><span class="hljs-params">( greet )</span></span></code></pre><p>如果这是一个类而不是一个模块，我们当然可以使用 <code>new</code> 方法从类创建对象 - 每个单独的对象，类的每个&#39;实例&#39; - 都可以访问实例方法。但是你无法创建模块的实例。那么我们如何使用它们的实例方法呢？这是引入 mixins 的时候了...</p>
<h3 id="-mixins-">包含模块或混入（Mixins）</h3>
<p>对象可以通过使用 <code>include</code> 方法包含该模块来访问模块的实例方法。如果你要将 MyModule 包含到程序中，则该模块内的所有内容都会突然出现在当前作用域内。因此，现在可以访问 MyModule 的 <code>greet</code> 方法：</p>
<div class="code-file clearfix"><span>modules2.rb</span></div>

<pre><code>include MyModule
<span class="hljs-function"><span class="hljs-title">puts</span><span class="hljs-params">( greet )</span></span></code></pre><p>注意，只会包含实例方法。在上面的示例中，已经包含了 <code>greet</code>（实例）方法，但是 <code>MyModule.greet</code>（模块）方法没有被包含...</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">MyModule</span></span>
  GOODMOOD = <span class="hljs-string">"happy"</span>
  BADMOOD = <span class="hljs-string">"grumpy"</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">greet</span></span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"I'm <span class="hljs-subst">#{GOODMOOD}</span>. How are you?"</span>
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">MyModule</span>.<span class="hljs-title">greet</span></span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"I'm <span class="hljs-subst">#{BADMOOD}</span>. How are you?"</span>
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span></code></pre><p>正如它所包含的那样，<code>greet</code> 方法可以像使用当前作用域中的普通实例方法一样被使用...</p>
<pre><code><span class="hljs-function"><span class="hljs-title">puts</span><span class="hljs-params">( greet )</span></span></code></pre><p>包含模块的过程也称为“混入”（mixing in） - 这解释了为什么包含的模块通常被称为 &quot;mixins&quot;。将模块混入到类定义中时，从该类创建的任何对象都将能够使用被混入模块的实例方法，就像它们在类本身中定义一样。</p>
<div class="code-file clearfix"><span>modules3.rb</span></div>

<pre><code><span class="hljs-keyword">class</span> <span class="hljs-symbol">MyClass</span>
  <span class="hljs-symbol">include</span> <span class="hljs-symbol">MyModule</span>

  <span class="hljs-symbol">def</span> <span class="hljs-symbol">sayHi</span>
    <span class="hljs-symbol">puts</span>( <span class="hljs-symbol">greet</span> )
  <span class="hljs-symbol">end</span>
<span class="hljs-symbol">end</span></code></pre><p>不仅这个类的方法可以访问 MyModule 模块的 <code>greet</code> 方法，而且从类中创建的任何对象也是如此：</p>
<pre><code>ob = MyClass<span class="hljs-selector-class">.new</span>
ob<span class="hljs-selector-class">.sayHi</span>
<span class="hljs-function"><span class="hljs-title">puts</span><span class="hljs-params">(ob.greet)</span></span></code></pre><p>模块（Modules）可以被认为是离散的代码单元，可以简化可重用代码库的创建。另一方面，你可能更感兴趣的是使用模块作为实现多继承的替代方式。</p>
<p>回到我在本章开头提到的一个示例，让我们假设你有一个剑（Sword）类，它不仅是一种武器（Weapon），也是一种珍宝（Treasure）。或许 Sword 是 Weapon 类的后代（因此继承了 Weapon 的 <code>deadliness</code> 属性），但它也需要具有 Treasure 的属性（例如 <code>value</code> 和 <code>owner</code>），并且这是拥有魔法（MagicThing）的精灵之剑。如果你在 Treasure 和 MagicThing <em>模块</em>（<code>modules</code>）而不是<em>类</em>（<code>classes</code>）中定义这些属性，则 Sword 类将能够以“混入”（mix in）方式包含这些模块其方法或属性：</p>
<div class="code-file clearfix"><span>modules4.rb</span></div>

<pre><code><span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">MagicThing</span></span>
  <span class="hljs-keyword">attr_accessor</span> <span class="hljs-symbol">:power</span>
<span class="hljs-keyword">end</span>

<span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">Treasure</span></span>
  <span class="hljs-keyword">attr_accessor</span> <span class="hljs-symbol">:value</span>
  <span class="hljs-keyword">attr_accessor</span> <span class="hljs-symbol">:owner</span>
<span class="hljs-keyword">end</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Weapon</span></span>
  <span class="hljs-keyword">attr_accessor</span> <span class="hljs-symbol">:deadliness</span>
<span class="hljs-keyword">end</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Sword</span> &lt; Weapon</span>
  <span class="hljs-keyword">include</span> Treasure
  <span class="hljs-keyword">include</span> MagicThing
  <span class="hljs-keyword">attr_accessor</span> <span class="hljs-symbol">:name</span>
<span class="hljs-keyword">end</span></code></pre><p>Sword 对象现在可以访问 Sword 类本身，它的祖先类 Weapon，以及它的混入（mixed-in）模块 Treasure 和 MagicThing 的方法和属性：</p>
<pre><code>s = Sword<span class="hljs-selector-class">.new</span>
s<span class="hljs-selector-class">.name</span> = <span class="hljs-string">"Excalibur"</span>
s<span class="hljs-selector-class">.deadliness</span> = <span class="hljs-string">"fatal"</span>
s<span class="hljs-selector-class">.value</span> = <span class="hljs-number">1000</span>
s<span class="hljs-selector-class">.owner</span> = <span class="hljs-string">"Gribbit The Dragon"</span>
s<span class="hljs-selector-class">.power</span> = <span class="hljs-string">"Glows when Orcs Appear"</span>
<span class="hljs-function"><span class="hljs-title">puts</span><span class="hljs-params">(s.name)</span></span>
<span class="hljs-function"><span class="hljs-title">puts</span><span class="hljs-params">(s.deadliness)</span></span>
<span class="hljs-function"><span class="hljs-title">puts</span><span class="hljs-params">(s.value)</span></span>
<span class="hljs-function"><span class="hljs-title">puts</span><span class="hljs-params">(s.owner)</span></span>
<span class="hljs-function"><span class="hljs-title">puts</span><span class="hljs-params">(s.power)</span></span></code></pre><p>顺便提一下，无法从模块外部访问模块中作为局部变量的任何变量。即使模块内部的方法试图访问局部变量并且该方法是由模块外部的代码调用的 - 例如，当包含混入模块时，情况也是如此：</p>
<div class="code-file clearfix"><span>mod_vars.rb</span></div>

<pre><code>x = <span class="hljs-number">1</span>                          <span class="hljs-comment"># local to this program</span>

<span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">Foo</span></span>
  x = <span class="hljs-number">50</span>                      <span class="hljs-comment"># local to module Foo</span>

  <span class="hljs-comment"># This can be mixed in but the variable x won't then be visible</span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">no_bar</span></span>
    <span class="hljs-keyword">return</span> x
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">bar</span></span>
    @x = <span class="hljs-number">1000</span>
    <span class="hljs-keyword">return</span> @x
  <span class="hljs-keyword">end</span>

  puts( <span class="hljs-string">"In Foo: x = <span class="hljs-subst">#{x}</span>"</span> ) <span class="hljs-comment"># this can access the „module local‟ x</span>
<span class="hljs-keyword">end</span>

<span class="hljs-keyword">include</span> Foo

puts(x)
puts( no_bar )                  <span class="hljs-comment"># Error! This can't access the module-local variable</span>
                             <span class="hljs-comment"># needed by the no_bar method</span>
puts(bar)</code></pre><p>请注意，<em>实例变量（instance variables）</em>可用于混入方法（例如 <code>bar</code>）。但是，即使在混入方法的当前作用域中存在具有相同名称的局部变量时，局部变量也不可用（因此，即使 <code>x</code> 在当前作用域中已经声明，<code>no_bar</code> 也无法访问名为 <code>x</code> 的变量）。</p>
<p>模块可以具有其自己的实例变量，这些变量仅仅属于模块“对象”。这些实例变量存在于模块方法的作用域内：</p>
<div class="code-file clearfix"><span>inst_class_vars.rb</span></div>

<pre><code><span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">X</span></span>
  @instvar = <span class="hljs-string">"X's @instvar"</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">self</span>.<span class="hljs-title">aaa</span></span>
    puts(@instvar)
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>

X.aaa <span class="hljs-comment">#=&gt; "X's <span class="hljs-doctag">@instvar</span>"</span></code></pre><p>但实例对象中引用的实例变量“属于”包含该模块的作用域：</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">X</span></span>
  @instvar = <span class="hljs-string">"X's @instvar"</span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">amethod</span></span>
    @instvar = <span class="hljs-number">10</span>             <span class="hljs-comment"># creates <span class="hljs-doctag">@instvar</span> in current scope</span>
    puts(@instvar)
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>

<span class="hljs-keyword">include</span> X

X.aaa                     <span class="hljs-comment">#=&gt; X's <span class="hljs-doctag">@instvar</span></span>
puts( @instvar )         <span class="hljs-comment">#=&gt; nil</span>
amethod <span class="hljs-comment">#=&gt; 10</span>
puts( @instvar )         <span class="hljs-comment">#=&gt; 10</span>
@instvar = <span class="hljs-string">"hello world"</span>
puts( @instvar )         <span class="hljs-comment">#=&gt; "hello world"</span></code></pre><p>类变量也会被混入，和实例变量一样，它们的值可以在当前作用域内重新分配：</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">X</span></span>
  @@classvar = <span class="hljs-string">"X's @@classvar"</span>
<span class="hljs-keyword">end</span>

<span class="hljs-keyword">include</span> X

puts( @@classvar )         <span class="hljs-comment">#=&gt; X's @<span class="hljs-doctag">@classvar</span></span>
@@classvar = <span class="hljs-string">"bye bye"</span>
puts( @@classvar )         <span class="hljs-comment">#=&gt; "bye bye"</span></code></pre><p>你可以使用 <code>instance_variables</code> 方法获取实例变量的名称数组：</p>
<pre><code><span class="hljs-function"><span class="hljs-title">p</span><span class="hljs-params">( X.instance_variables )</span></span>
<span class="hljs-function"><span class="hljs-title">p</span><span class="hljs-params">( self.instance_variables )</span></span></code></pre><h3 id="-">命名冲突</h3>
<p>模块方法（特定的前缀为模块名称的那些方法）可以让你的代码免受意外命名冲突的影响。但是，模块中的实例方法没有这样的保护措施。假设你有两个模块 - 一个叫做 Happy，另一个叫做 Sad。它们每个都包含一个名为 <code>mood</code> 的模块方法和一个名为 <code>expression</code> 的实例方法。</p>
<div class="code-file clearfix"><span>happy_sad.rb</span></div>

<pre><code><span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">Happy</span></span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">Happy</span>.<span class="hljs-title">mood</span> <span class="hljs-comment"># module method</span></span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"happy"</span>
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">expression</span> <span class="hljs-comment"># instance method</span></span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"smiling"</span>
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>

<span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">Sad</span></span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">Sad</span>.<span class="hljs-title">mood</span>  <span class="hljs-comment"># module method</span></span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"sad"</span>
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">expression</span> <span class="hljs-comment"># instance method</span></span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"frowning"</span>
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span></code></pre><p>现在，一个类 Person 包含了这两个模块：</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Person</span></span>
  <span class="hljs-keyword">include</span> Happy
  <span class="hljs-keyword">include</span> Sad
  <span class="hljs-keyword">attr_accessor</span> <span class="hljs-symbol">:mood</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">initialize</span></span>
    @mood = Happy.mood
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span></code></pre><p>Person 类的 <code>initialize</code> 方法需要使用被包含模块之一的 <code>mood</code> 方法设置其 <code>@mood</code> 变量的值。实际上他们都有一个 <code>mood</code> 方法，但这没有问题；作为一个模块方法，<code>mood</code> 必须以模块名称开头，因此 <code>Happy.mood</code> 不会与 <code>Sad.mood</code> 混淆。</p>
<p>但 Happy 和 Sad 模块也都包含一个名为 <code>expression</code> 的方法。这是一个实例方法，当两个模块都包含在 Person 类中时，可以不带任何限定地调用 <code>expression</code> 方法：</p>
<pre><code>p1 = Person<span class="hljs-selector-class">.new</span>
<span class="hljs-function"><span class="hljs-title">puts</span><span class="hljs-params">(p1.expression)</span></span></code></pre><p>对象 <code>p1</code> 使用哪个 <code>expression</code> 方法？事实证明它使用最后定义的方法。在目前的情况下，这恰好是 Sad 模块中定义的方法，原因很简单，在 Happy 之后包含了 Sad 模块。如果更改包含顺序以在 Sad 之后包含 Happy，则 <code>p1</code> 对象将使用 Happy 模块中定义的 <code>expression</code> 方法的版本。</p>
<p>在开始创建大型而且复杂的模块并将其混入到你的常规基类中之前，请记住这个潜在的问题 - 即包含相同名称的实例方法将“覆盖”彼此。在我的小程序中发现问题可能是显而易见的。但在一个巨大的应用程序中它可能不那么明显！</p>
<h3 id="alias-">Alias 方法</h3>
<p>当你使用来自多个模块有类似命名的方法时，避免歧义的一种方式是给这些方法一个“别名”（alias）。别名是具有新名称的现有方法的副本。你可以使用 <code>alias</code> 关键字后跟新名称，以及旧名称：</p>
<div class="code-file clearfix"><span>alias_methods.rb</span></div>

<pre><code><span class="hljs-keyword">alias</span> happyexpression <span class="hljs-keyword">expression</span></code></pre><p>你还可以使用别名（alias）来创建一个已被覆盖方法的副本，以便你可以在定义被覆盖之前指定引用其版本：</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">Happy</span></span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">Happy</span>.<span class="hljs-title">mood</span></span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"happy"</span>
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">expression</span></span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"smiling"</span>
  <span class="hljs-keyword">end</span>
  <span class="hljs-keyword">alias</span> happyexpression expression
<span class="hljs-keyword">end</span>

<span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">Sad</span></span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">Sad</span>.<span class="hljs-title">mood</span></span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"sad"</span>
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">expression</span></span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"frowning"</span>
  <span class="hljs-keyword">end</span>
  <span class="hljs-keyword">alias</span> sadexpression expression
<span class="hljs-keyword">end</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Person</span></span>
  <span class="hljs-keyword">include</span> Happy
  <span class="hljs-keyword">include</span> Sad
  <span class="hljs-keyword">attr_accessor</span> <span class="hljs-symbol">:mood</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">initialize</span></span>
    @mood = Happy.mood
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>

p2 = Person.new
puts(p2.mood)                <span class="hljs-comment">#=&gt; happy</span>
puts(p2.expression)         <span class="hljs-comment">#=&gt; frowning</span>
puts(p2.happyexpression)     <span class="hljs-comment">#=&gt; smiling</span>
puts(p2.sadexpression)         <span class="hljs-comment">#=&gt; frowning</span></code></pre><h3 id="-mix-in-">谨慎使用 Mix-in！</h3>
<p>虽然每个类只能从继承自一个超类，但它可以混入（mix in）许多模块。实际上，完全允许将一批模块混入到另一批模块中，并将这些其它模块混入到类中，再将这些类混入到更多模块中。下面是一些代码的示例，这些代码将子类混入在模块中，甚至是混入到模块中的子类中。此代码已被有意简化。有关示例的完整代码，请参阅示例程序 <strong>multimods.rb</strong>：</p>
<div class="code-file clearfix"><span>multimods.rb</span></div>

<pre><code>module MagicThing # module
  <span class="hljs-keyword">class</span> <span class="hljs-symbol">MagicClass</span> # <span class="hljs-symbol">class</span> <span class="hljs-symbol">inside</span> <span class="hljs-symbol">module</span>
  <span class="hljs-symbol">end</span>
<span class="hljs-symbol">end</span>

<span class="hljs-symbol">module</span> <span class="hljs-symbol">Treasure</span>   # <span class="hljs-symbol">module</span>
<span class="hljs-symbol">end</span>

<span class="hljs-symbol">module</span> <span class="hljs-symbol">MetalThing</span>
  <span class="hljs-symbol">include</span> <span class="hljs-symbol">MagicThing</span>             # <span class="hljs-symbol">mixin</span>
  <span class="hljs-symbol">class</span> <span class="hljs-symbol">Attributes</span> &lt; <span class="hljs-symbol">MagicClass</span> # <span class="hljs-symbol">subclasses</span> <span class="hljs-symbol">class</span> <span class="hljs-symbol">from</span> <span class="hljs-symbol">mixin</span>
  <span class="hljs-symbol">end</span>
<span class="hljs-symbol">end</span>

<span class="hljs-symbol">include</span> <span class="hljs-symbol">MetalThing</span>                # <span class="hljs-symbol">mixin</span>
<span class="hljs-symbol">class</span> <span class="hljs-symbol">Weapon</span> &lt; <span class="hljs-symbol">MagicClass</span>         # <span class="hljs-symbol">subclass</span> <span class="hljs-symbol">class</span> <span class="hljs-symbol">from</span> <span class="hljs-symbol">mixin</span>
  <span class="hljs-symbol">class</span> <span class="hljs-symbol">WeaponAttributes</span> &lt; <span class="hljs-symbol">Attributes</span> # <span class="hljs-symbol">subclass</span>
  <span class="hljs-symbol">end</span>
<span class="hljs-symbol">end</span>

<span class="hljs-symbol">class</span> <span class="hljs-symbol">Sword</span> &lt; <span class="hljs-symbol">Weapon</span> # <span class="hljs-symbol">subclass</span>
  <span class="hljs-symbol">include</span> <span class="hljs-symbol">Treasure</span>         # <span class="hljs-symbol">mixin</span>
  <span class="hljs-symbol">include</span> <span class="hljs-symbol">MagicThing</span>     # <span class="hljs-symbol">mixin</span>
<span class="hljs-symbol">end</span></code></pre><p>简而言之，虽然模块在使用时可能有助于避免与 C++ 一样的多重继承相关的一些复杂性，但它们仍可能被滥用。如果程序员真的想要创建复杂的类层次结构，并且在模块中的多个级别上具有难以理解的依赖性，那么他或她当然可以这样做。在 <strong>multimods.rb</strong> 中，我已经展示了用几行代码编写一个难以理解的程序是多么容易。想象一下，你可能将成千上万行这样的代码分散在数十个代码文件中！可以说，这不是我推荐的编程风格，因此你可能需要在混入模块之前仔细考虑一下。</p>
<h3 id="-">从文件中包含模块</h3>
<p>到目前为止，我已经混入了在单个源文件中定义的模块。通常，在单独的文件中定义模块并根据需要将它们混入更有用。要使用其它文件中的代码，你必须要做的第一件事是使用 <code>require</code> 方法加载该文件，如下所示：</p>
<pre><code><span class="hljs-function"><span class="hljs-title">require</span><span class="hljs-params">( <span class="hljs-string">"testmod.rb"</span> )</span></span></code></pre><p>（可选）你可以省略文件扩展名：</p>
<pre><code><span class="hljs-built_in">require</span>( <span class="hljs-string">"testmod"</span> )  <span class="hljs-comment"># this works too</span></code></pre><p>所需文件必须位于当前目录，搜索路径或预定义数组变量 <code>$:</code> 中列出的文件夹中。你可以使用常用的 array-append 方法 <code>&lt;&lt;</code> 向此数组变量添加元素，以这种方式：</p>
<pre><code><span class="hljs-symbol">$:</span> &lt;&lt; <span class="hljs-string">"C:/mydir"</span></code></pre><div class="note">

<p>全局变量 <code>$:</code>（美元符号和冒号）包含一个字符串数组，表示 Ruby 在查找加载或引入文件时搜索的目录。</p>
</div>

<p>如果成功加载指定的文件，<code>require</code> 方法返回 <code>true</code> 值；否则返回 <code>false</code>。如果有疑问，你只需显示结果：</p>
<pre><code><span class="hljs-function"><span class="hljs-title">puts</span>(<span class="hljs-title">require</span>( <span class="hljs-string">"testmod.rb"</span> ))</span></code></pre><p>当需要该文件时，将执行在文件运行时通常执行的任意代码。因此，如果文件 <code>testmod.rb</code> 包含此代码...</p>
<div class="code-file clearfix"><span>testmod.rb</span></div>

<pre><code><span class="hljs-variable">def</span> <span class="hljs-variable">sing</span>
  <span class="hljs-function"><span class="hljs-title">puts</span>( <span class="hljs-string">"Tra-la-la-la-la...."</span>)</span>
<span class="hljs-variable">end</span>

<span class="hljs-function"><span class="hljs-title">puts</span>( <span class="hljs-string">"module loaded"</span>)</span>
<span class="hljs-variable">sing</span></code></pre><p>...文件 <strong>require_module.rb</strong> 包含它...</p>
<div class="code-file clearfix"><span>require_module.rb</span></div>

<pre><code><span class="hljs-function"><span class="hljs-title">require</span><span class="hljs-params">( <span class="hljs-string">"testmod.rb"</span>)</span></span></code></pre><p>...然后，当运行 <strong>require_module.rb</strong> 时，这将输出：</p>
<pre><code>module loaded
Tra-<span class="hljs-keyword">la</span>-<span class="hljs-keyword">la</span>-<span class="hljs-keyword">la</span>-<span class="hljs-keyword">la</span>....</code></pre><p>在所需文件中声明的模块，可以将其混入：</p>
<pre><code><span class="hljs-function"><span class="hljs-title">require</span><span class="hljs-params">( <span class="hljs-string">"testmod.rb"</span>)</span></span>
include MyModule <span class="hljs-selector-id">#mix</span> <span class="hljs-keyword">in</span> MyModule declared <span class="hljs-keyword">in</span> testmod.rb</code></pre><div class="code-file clearfix"><span>load_module.rb</span></div>

<p>Ruby 还允许你使用 <code>load</code> 方法加载文件。在大多数情况下，<code>require</code> 和 <code>load</code> 可视为可互换的。但是有一些微妙的差异。特别是，<code>load</code> 可以使用可选的第二个参数，如果为 <code>true</code>，则加载并执行代码作为未命名或匿名模块：</p>
<pre><code><span class="hljs-function"><span class="hljs-title">load</span><span class="hljs-params">( <span class="hljs-string">"testmod.rb"</span>, true)</span></span></code></pre><p>加载的文件不会将新命名空间（namespace）引入主程序，你将无法访问已加载文件中的所有模块。当 <code>load</code> 的第二个参数为 <code>false</code> 或没有第二个参数时，你将可以访问已加载文件中的模块。请注意，使用 <code>load</code> 时你必须输入完整的文件名（&quot;testmod&quot; 减去 &quot;.rb&quot; 扩展名是不行的）。</p>
<p>另一个区别是 <code>require</code> 仅加载一次文件（即使你的代码多次引入该文件），而 <code>load</code> 会导致每次调用 <code>load</code> 时重新加载指定的文件。我们假设你在文件 <strong>test.rb</strong> 中有这个：</p>
<div class="code-file clearfix"><span>test.rb</span></div>

<pre><code>MyConst = <span class="hljs-number">1</span>
<span class="hljs-keyword">if</span> @a == <span class="hljs-literal">nil</span> <span class="hljs-keyword">then</span>
  @a = <span class="hljs-number">1</span>
<span class="hljs-keyword">else</span>
  @a += MyConst
<span class="hljs-keyword">end</span>

puts @a</code></pre><p>我们假设你现在引入（require）这个文件三次：</p>
<div class="code-file clearfix"><span>require_again.rb</span></div>

<pre><code><span class="hljs-built_in">require</span> <span class="hljs-string">"test"</span>
<span class="hljs-built_in">require</span> <span class="hljs-string">"test"</span>
<span class="hljs-built_in">require</span> <span class="hljs-string">"test"</span></code></pre><p>这里将会输出：</p>
<pre><code><span class="hljs-number">1</span></code></pre><p>但是如果你加载（load）文件三次...</p>
<div class="code-file clearfix"><span>load_again.rb</span></div>

<pre><code><span class="hljs-keyword">load</span> <span class="hljs-string">"test.rb"</span>
<span class="hljs-keyword">load</span> <span class="hljs-string">"test.rb"</span>
<span class="hljs-keyword">load</span> <span class="hljs-string">"test.rb"</span></code></pre><p>...那么这将会输出：</p>
<pre><code><span class="hljs-number">1</span>
./test.rb:<span class="hljs-number">1</span>: warning: already initialized constant MyConst
<span class="hljs-number">2</span>
./test.rb:<span class="hljs-number">1</span>: warning: already initialized constant MyConst
<span class="hljs-number">3</span></code></pre><h2 id="-">深入探索</h2>
<h3 id="-">模块与类</h3>
<p>我们已经研究了模块的行为。现在我们来看看模块的本质是什么。事实证明，与 Ruby 中的大多数其它东西一样，模块是对象（object）。事实上，每个命名模块都是 Module 类的实例：</p>
<div class="code-file clearfix"><span>module_inst.rb</span></div>

<pre><code><span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">MyMod</span></span>
<span class="hljs-keyword">end</span>

puts( MyMod.<span class="hljs-keyword">class</span> )  <span class="hljs-comment">#=&gt; Module</span></code></pre><p>你无法创建命名模块的后代，因此不允许这样做：</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">MyMod</span></span>
<span class="hljs-keyword">end</span>

<span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">MyOtherMod</span> &lt; MyMod</span>
<span class="hljs-keyword">end</span></code></pre><p>但是，与其它类一样，允许创建 Module 类的后代：</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">X</span> &lt; Module</span>
<span class="hljs-keyword">end</span></code></pre><p>实际上，<code>Class</code> 类本身就是 <code>Module</code> 类的后代。它继承了 Module 的行为并添加了一些重要的新行为 - 特别是创建对象的能力。你可以通过运行 <strong>modules_classes.rb</strong> 程序来显示此继承链以验证 Module 是 Class 的超类：</p>
<div class="code-file clearfix"><span>modules_classes.rb</span></div>

<pre><code><span class="hljs-keyword">Class</span>
Module  #=&gt; <span class="hljs-keyword">is</span> the superclass <span class="hljs-keyword">of</span> <span class="hljs-keyword">Class</span>
<span class="hljs-keyword">Object</span>  #=&gt; <span class="hljs-keyword">is</span> the superclass <span class="hljs-keyword">of</span> Module</code></pre><h3 id="-">预定义模块</h3>
<p>以下模块内置于 Ruby 解释器中：</p>
<pre><code>Comparable, Enumerable, FileTest, GC, Kernel, Math, ObjectSpace, Precision, <span class="hljs-keyword">Process</span>, <span class="hljs-keyword">Signal</span></code></pre><p><code>Comparable</code> 是一个 mixin 模块，允许包含类以实现比较运算符。包含类必须定义 <code>&lt;=&gt;</code> 运算符，该运算符将接收器对象与另一个对象进行比较，返回 -1，0 或 +1，具体取决于接收器对象是否小于，等于或大于另一个对象。Comparable 使用 <code>&lt;=&gt;</code> 来实现常规的比较运算符（<code>&lt;</code>，<code>&lt;=</code>，<code>==</code>，<code>&gt;=</code> 和 <code>&gt;</code>）和 <code>between?</code> 方法。</p>
<p><code>Enumerable</code> 是为枚举（enumeration）提供的 mix-in 模块。包含类必须提供 <code>each</code> 方法。</p>
<p><code>FileTest</code> 是一个包含文件测试功能的模块；它的方法也可以从 File 类访问。</p>
<p><code>GC</code> 模块为 Ruby 的标记和垃圾回收清除机制提供了一个接口。一些底层方法也可以通过 ObjectSpace 模块获得。</p>
<p><code>Kernel</code> 是 Object 类包含的模块；它定义了 Ruby 的“内置”（‘built-in）方法。</p>
<p><code>Math</code> 是一个包含了基本三角函数和超越函数功能的模块函数（module functions）的模块。它具有相同定义和名称的“实例方法”（instance methods）和模块方法。</p>
<p><code>ObjectSpace</code> 是一个包含了与垃圾回收工具交互的例程，并且允许你使用迭代器遍历所有活动对象的模块。</p>
<p><code>Precision</code> 是为有具体精度的数字（numeric）类提供的 mixin 模块。这里，&quot;Precision&quot;　表示近似的实数精度，因此，该模块不应包含在不是　Real（实数）子集的任何类中（因此它不应包含在诸如　Complex（复数）或　Matrix（矩阵）之类的类中）。</p>
<p><code>Process</code> 是操纵进程（processes）的模块。它的所有方法都是模块方法（module methods）。</p>
<p><code>Signal</code> 是用于处理发送到正在运行的进程的信号的模块。可用信号名称列表及其解释取决于操作系统。</p>
<p>以下是三个最常用的Ruby模块的简要概述...</p>
<h4 id="kernel">Kernel</h4>
<p>最重要的预定义模块是 Kernel，它提供了许多“标准”（standard）Ruby 方法，如 <code>gets</code>，<code>puts</code>，<code>print</code> 和 <code>require</code>。与许多 Ruby 类库一样，Kernel 是用 C 语言编写的。虽然 Kernel 实际上是“内置于”（built into）Ruby 解释器，但从概念上讲它可以被视为一个 mixed-in 模块，就像普通的 Ruby mixin 一样，它使得它的方法可以直接用于任何需要它的类。由于它混入到了 Object 类中，所有其它 Ruby 类都继承自该类，因此 Kernel 的方法是全部可访问的。</p>
<h4 id="math">Math</h4>
<div class="code-file clearfix"><span>math.rb</span></div>

<p>Math 模块的方法以“模块”（module）和“实例”（instance）方法的形式同时提供，因此可以通过将 Math 混入到类中或从外部通过使用模块名称，点和方法名来访问模块方法；你可以使用双冒号访问常量：</p>
<pre><code>puts( <span class="hljs-name">Math</span>.sqrt(<span class="hljs-number">144</span>) )
puts( <span class="hljs-name">Math</span>:<span class="hljs-symbol">:PI</span> )</code></pre><h4 id="comparable">Comparable</h4>
<div class="code-file clearfix"><span>compare.rb</span></div>

<p>Comparable 模块通过将模块混入到你的类中并定义 <code>&lt;=&gt;</code> 方法，来提供一种巧妙的方式定义你自己的比较&#39;运算符&#39;（operators）<code>&lt;</code>，<code>&lt;=</code>，<code>==</code>，<code>&gt;=</code> 和 <code>&gt;</code>。然后，你可以指定将当前对象中的某些值与其他值进行比较的规则。例如，你可能会比较两个整数，两个字符串的长度或一些更不常用的值（例如数组中字符串的位置）。我在我的示例程序 <strong>compare.rb</strong> 中选择了这种不常用的比较类型。这里使用了一个虚构的数组中的字符串索引，以便将一个人的名字与另一个人的名字进行比较。小的索引（例如位于索引 0 处的 &quot;hobbit&quot;）被认为是小于大的索引（例如位于索引 6 处的 &quot;dragon&quot;）：</p>
<pre><code><span class="hljs-keyword">class</span> Being
  <span class="hljs-keyword">include</span> Comparable
  BEINGS = [<span class="hljs-string">'hobbit'</span>,<span class="hljs-string">'dwarf'</span>,<span class="hljs-string">'elf'</span>,<span class="hljs-string">'orc'</span>,<span class="hljs-string">'giant'</span>,<span class="hljs-string">'oliphant'</span>,<span class="hljs-string">'dragon'</span>]

  attr_accessor :<span class="hljs-type">name</span>

  def &lt;=&gt; (anOtherName)
    BEINGS.<span class="hljs-keyword">index</span>[@<span class="hljs-type">name</span>]&lt;=&gt;BEINGS.<span class="hljs-keyword">index</span>[anOtherName]
  <span class="hljs-keyword">end</span>

  def initialize( aName )
    @<span class="hljs-type">name</span> = aName
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>

elf = Being.<span class="hljs-built_in">new</span>(<span class="hljs-string">'elf'</span>)
orc = Being.<span class="hljs-built_in">new</span>(<span class="hljs-string">'orc'</span>)
giant = Being.<span class="hljs-built_in">new</span>(<span class="hljs-string">'giant'</span>)

puts( elf.name &lt; orc.name )     #=&gt; <span class="hljs-keyword">true</span>
puts( elf.name &gt; giant.name )     #=&gt; <span class="hljs-keyword">false</span></code></pre><h3 id="-">作用域解析</h3>
<p>与类一样，你可以使用双冒号作用域解析运算符（scope resolution operator）来访问模块内声明的常量（包括类和其它模块）。例如，假设你有嵌套的模块和类，如下所示：</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">OuterMod</span></span>
  moduleInnerMod
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Class1</span></span>
    <span class="hljs-keyword">end</span>
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span></code></pre><p>你可以使用 <code>::</code> 运算符访问 Class1，如下所示：</p>
<pre><code>OuterMod::InnerMod::Class1</code></pre><div class="note">

<p>有关类中常量的作用域解析的介绍，请参见第 2 章...</p>
</div>

<p>每个模块和类都有自己的作用域，这意味着可以在不同的作用域中使用单个常量名称。既然如此，你可以使用 <code>::</code> 运算符在确定的作用域内指定常量：</p>
<pre><code>Scope1::Scope2::Scope3     #...etc</code></pre><p>如果在常量名称的最开头使用此运算符，则会产生“打破”（breaking out）当前作用域并访问“顶级”（top level）作用域的效果：</p>
<pre><code>::ACONST     # refers to ACONST at „top level‟ scope</code></pre><p>以下程序提供了作用域运算符的一些示例：</p>
<div class="code-file clearfix"><span>scope_resolution.rb</span></div>

<pre><code>ACONST = <span class="hljs-string">"hello"</span>         <span class="hljs-comment"># We'll call this the "top-level" constant</span>

<span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">OuterMod</span></span>
  <span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">InnerMod</span></span>
    ACONST=<span class="hljs-number">10</span>
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Class1</span></span>
      <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Class2</span></span>
        <span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">XYZ</span></span>
          <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ABC</span></span>
            ACONST=<span class="hljs-number">100</span>
            <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">xyz</span></span>
              puts( <span class="hljs-symbol">:</span><span class="hljs-symbol">:ACONST</span> ) <span class="hljs-comment">#&lt;= this prints the „top-level‟ constant</span>
            <span class="hljs-keyword">end</span>
          <span class="hljs-keyword">end</span>
        <span class="hljs-keyword">end</span>
      <span class="hljs-keyword">end</span>
    <span class="hljs-keyword">end</span>
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>

puts(OuterMod::InnerMod::ACONST)
<span class="hljs-comment">#=&gt; displays 10</span>

puts(OuterMod::InnerMod::Class1::Class2::XYZ::ABC::ACONST)
<span class="hljs-comment">#=&gt; displays 100</span>

ob = OuterMod::InnerMod::Class1::Class2::XYZ::ABC.new
ob.xyz
<span class="hljs-comment">#=&gt; displays hello</span></code></pre><h3 id="-">模块函数</h3>
<div class="code-file clearfix"><span>module_func.rb</span></div>

<p>如果你希望函数既可用作实例方法又可用作模块方法，则可以使用 <code>module_function</code> 方法，传入与实例方法的名称相匹配的符号（symbol）即可，如下所示：</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">MyModule</span></span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">sayHi</span></span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"hi!"</span>
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">sayGoodbye</span></span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"Goodbye"</span>
  <span class="hljs-keyword">end</span>

  module_function <span class="hljs-symbol">:sayHi</span>
<span class="hljs-keyword">end</span></code></pre><p>现在，<code>sayHi</code> 方法可以混入到一个类中并用作实例方法：</p>
<pre><code><span class="hljs-keyword">class</span> <span class="hljs-symbol">MyClass</span>
  <span class="hljs-symbol">include</span> <span class="hljs-symbol">MyModule</span>
  <span class="hljs-symbol">def</span> <span class="hljs-symbol">speak</span>
    <span class="hljs-symbol">puts</span>(<span class="hljs-symbol">sayHi</span>)
    <span class="hljs-symbol">puts</span>(<span class="hljs-symbol">sayGoodbye</span>)
  <span class="hljs-symbol">end</span>
<span class="hljs-symbol">end</span></code></pre><p>它可以用作模块方法，使用点符号：</p>
<pre><code>ob = MyClass<span class="hljs-selector-class">.new</span>
ob<span class="hljs-selector-class">.speak</span>
<span class="hljs-function"><span class="hljs-title">puts</span><span class="hljs-params">(MyModule.sayHi)</span></span></code></pre><p>由于这里的 <code>sayGoodbye</code> 方法不是模块函数（module function），因此不能以这种方式使用：</p>
<pre><code>puts(MyModule.sayGoodbye)<span class="hljs-meta">  #=&gt; Error: undefined</span>
<span class="hljs-function"><span class="hljs-keyword">method</span></span></code></pre><p>Ruby 在其一些标准模块，例如 Math（在 Ruby 库文件，<strong>complex.rb</strong> 中）），中使用 <code>module_function</code> 来创建模块和实例方法的“匹配对”（matching pairs）。</p>
<h3 id="-">扩展对象</h3>
<p>你可以使用 <code>extend</code> 方法将模块的方法添加到特定对象（而不是整个类），如下所示：</p>
<div class="code-file clearfix"><span>extend.rb</span></div>

<pre><code><span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">A</span></span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">method_a</span></span>
    puts( <span class="hljs-string">'hello from a'</span> )
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyClass</span></span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">mymethod</span></span>
    puts( <span class="hljs-string">'hello from mymethod of class MyClass'</span> )
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>

ob = MyClass.new
ob.mymethod
ob.extend(A)</code></pre><p>现在对象 <code>ob</code> 用模块 <code>A</code> 进行了扩展（extend），它可以访问该模块的实例方法 <code>method_a</code>：</p>
<pre><code><span class="hljs-selector-tag">ob</span><span class="hljs-selector-class">.method_a</span></code></pre><p>实际上，你可以一次用多个模块来扩展对象。这里，模块 <code>B</code> 和 <code>C</code> 扩展了对象 <code>ob</code>：</p>
<pre><code><span class="hljs-symbol">ob.extend</span>(<span class="hljs-keyword">B, </span>C)</code></pre><p>当使用包含了与对象类中方法同名的方法的模块扩展对象时，模块中的方法将替换该类中的方法。所以，我们假设 <code>ob</code> 用这个类来扩展...</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">C</span></span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">mymethod</span></span>
    puts( <span class="hljs-string">'hello from mymethod of module C'</span> )
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span></code></pre><p>现在，当你调用 <code>ob.mymethod</code> 时，将显示字符串 &#39;hello from mymethod of module C&#39; 而不是之前显示的 &#39;hello from mymethod of class MyClass&#39;。</p>
<p>你可以通过使用 <code>freeze</code> 方法来“冻结”（freezing）它，以阻止对象被扩展：</p>
<pre><code>ob.<span class="hljs-keyword">freeze</span></code></pre><p>任何进一步扩展此对象的尝试都将导致运行时错误（runtime error）。为了避免这样的错误，你可以使用 <code>frozen?</code> 方法来测试对象是否已被冻结：</p>
<pre><code><span class="hljs-keyword">if</span> !(ob.frozen?)
  ob.extend( D )
  ob.method_d
<span class="hljs-keyword">else</span>
  puts( <span class="hljs-string">"Can't extend a frozen object"</span> )
<span class="hljs-keyword">end</span></code></pre>